

public class DataParser{
  
  int rowCount;
  int columnCount;
  float[][] data;
  String[] rowNames;
  String[] columnNames;
 
  int monthColumn = 0;
  int dayColumn = 1;
  int timeColumn = 2;
  int latitudeColumn = 3;
  int longitudeColumn = 4;
  int magnitudeColumn = 5;
  int depthColumn = 6;
  int hourColumn = 2;
  
  int firstRow;
  int lastRow;
  int[] mAndYRows;
  float minLat, maxLat;
  float minLon, maxLon;
  int minLatIdx;
  int maxLatIdx;
  int minLonIdx;
  int maxLonIdx;
  
  int[][] magnitudeFrequency;
  boolean[] magnitudeSelected;
  int[][] depthFrequency;
  boolean[] depthSelected;
  int[][] localTimeFrequency;
  boolean[] localTimeSelected;
  int[][] utcFrequency;
  boolean[] utcSelected;
  int[][] chiTimeFrequency;
  boolean[] chiTimeSelected;
  
  int mode;
  int magMode = 0;
  int depthMode = 1;
  int timeMode = 2;
  
  int[] quakesToDraw;
  int defaultQTDSize;
  
  ArrayList[][] quakeByLocation;
  int[][] quakeIdxByRow;
  int latIdxCol = 0;
  int lonIdxCol = 1;
  int kIdxCol = 2;
  
  int[] quakeMagColors; 
  int[] depthColors;
  int[] timeOfDayColors;
  
  double zoomLevel;
  float indicesPerLatDegree = 1;
  float indicesPerLonDegree = 1;
  int qPerClusterToDraw;
  
  int[] yearRowBoundary;
  
  float quakeModeSize = 7.0;
  float allQuakeModeSize = 5.0;
  
  float bigQuakeCount;
  
  DataParser(String filename){
    String[] rows = loadStrings(filename);
    
    String[] columns = split(rows[0], ',');
    columnNames = subset(columns, 1); // upper-left corner ignored
    scrubQuotes(columnNames);
    columnCount = columnNames.length;

    rowNames = new String[rows.length-1];
    data = new float[rows.length-1][];
    
    // start reading at row 1, because the first row was only the column headers
    for (int i = 1; i < rows.length; i++) {
      if (trim(rows[i]).length() == 0) {
        continue; // skip empty rows
      }
      if (rows[i].startsWith("#")) {
        continue;  // skip comment lines
      }

      // split the row on the commas
      String[] pieces = split(rows[i], ',');
      //scrubQuotes(pieces);
      
      // copy row title
      rowNames[rowCount] = pieces[0];
      // copy data into the table starting at pieces[1]
      data[rowCount] = parseFloat(subset(pieces, 1));

      // increment the number of valid rows found so far
      rowCount++;      
    }
    // resize the 'data' array as necessary
    data = (float[][]) subset(data, 0, rowCount);
    
    println("done loading quakes.  Total quake number = " + rowCount);// + count);
   
    firstRow = 0;
    lastRow = rowCount;
    minLatIdx = 0;
    minLonIdx = 0;
    maxLatIdx = (int) (indicesPerLatDegree*180+1);
    maxLonIdx = (int) (indicesPerLonDegree*360+1);
    
    magnitudeSelected = new boolean[7];
    for(int i = 0; i < magnitudeSelected.length; i++){
      magnitudeSelected[i] = true;
    }
    
    depthSelected = new boolean[3]; 
    for(int i = 0; i < depthSelected.length; i++){
      depthSelected[i] = true;
    }
    
    localTimeSelected = new boolean[3];
    utcSelected = new boolean[3];
    chiTimeSelected = new boolean[3];
    for(int i = 0; i < localTimeSelected.length; i++){
      localTimeSelected[i] = true;
      utcSelected[i] = true;
      chiTimeSelected[i] = true;
    }
    
    quakeMagColors = new int[7];
    quakeMagColors[0] = 0xFFFEE5D9;
    quakeMagColors[1] = 0xFFFCBBA1;
    quakeMagColors[2] = 0xFFFC9272;
    quakeMagColors[3] = 0xFFFB6A4A;
    quakeMagColors[4] = 0xFFEF3B2C;
    quakeMagColors[5] = 0xFFCB181D;
    quakeMagColors[6] = 0xFF99000D;
    
    depthColors = new int[3]; 
    depthColors[0] = 0xFFEFEDF5;
    depthColors[1] = 0xFFBCBDDC;
    depthColors[2] = 0xFF756BB1;
    
    timeColors = new int[3];
    timeColors[0] = 0xFFEDF8B1;
    timeColors[1] = 0xFF7FCDBB;
    timeColors[2] = 0xFF2C7FB8;
      
    yearRowBoundary = new int[38];
    mAndYRows = new int[(2010-1973)*12 + 6+1];//why the plus one?  i don't know
    
    initQuakeArrays();
    qPerClusterToDraw = 1;
    
    //freqSanityCheck();
  }
  
  void scrubQuotes(String[] array) {
    for (int i = 0; i < array.length; i++) {
      if (array[i].length() > 2) {
        // remove quotes at start and end, if present
        if (array[i].startsWith("\"") && array[i].endsWith("\"")) {
          array[i] = array[i].substring(1, array[i].length() - 1);
        }
      }
      // make double quotes into single quotes
      array[i] = array[i].replaceAll("\"\"", "\"");
    }
  }
  
  int numArrayLists;
  
  //freqPerCat[ magCategory][ month] hold cum freq
  void initQuakeArrays(){
    numArrayLists= 0; //just to keep track, not used
    println("starting to store quakes");
    
    quakeIdxByRow = new int[ rowCount ][3];
    quakeByLocation = new ArrayList[(int)(indicesPerLatDegree*180+1)][(int) (indicesPerLonDegree*360+1)];
    
    magnitudeFrequency = new int[7][12*(2010-1973)+6+1];
    depthFrequency = new int[3][12*(2010-1973)+6+1];
    localTimeFrequency = new int[3][12*(2010-1973)+6+1];
    utcFrequency = new int[3][12*(2010-1973)+6+1];
    chiTimeFrequency = new int[3][12*(2010-1973)+6+1];
      
    float prevMonth = data[0][monthColumn]-1; //-1 b/x months start at 1
    int prevYear = Integer.parseInt(getRowName(0)); 
    yearRowBoundary[0] = 0; //no boundary between 1973 and 1972
    mAndYRows[0] = 0; //first month starts at idx 0
    int monthIdx = 0;
    for(int row = 0; row < rowCount; row++){ 
      float lat = data[row][latitudeColumn];
      float lon = data[row][longitudeColumn];
      float mag = data[row][magnitudeColumn];
      float month = data[row][monthColumn]-1;
      float year = Integer.parseInt(getRowName( row) );
  
      //fill magnitude and depth frequency arrays
      if( month != prevMonth ){ //if new month, increment month index
        monthIdx++;
        prevMonth = month;
        mAndYRows[monthIdx] = row; //idx = 1 = first time month != 0, row at this point stored
      }
      if( year != prevYear ){
        yearRowBoundary[ (int) year-1973] = (int) row;
        prevYear = (int) year;
      }
      if( mag < 4 ){
        magnitudeFrequency[0][monthIdx]++;
      } else{
        magnitudeFrequency[(int) mag-3][monthIdx]++;
      }
      float depth = data[row][depthColumn];
      if( depth < 70 ){
        depthFrequency[0][monthIdx]++;
      }else if( depth < 300 ){
        depthFrequency[1][monthIdx]++;
      }else{
        depthFrequency[2][monthIdx]++;
      }
      
      float utcHour = data[row][hourColumn];
      float localHour = convertToLocalTime(lat, lon, utcHour );
      float chiHour = convertToChiTime(utcHour);
      
      if( utcHour < 8 ){
        utcFrequency[0][monthIdx]++;
      } else if( utcHour < 16 ){
        utcFrequency[1][monthIdx]++;
      } else{
        utcFrequency[2][monthIdx]++;
      }
      
      if( chiHour < 8 ){
        chiTimeFrequency[0][monthIdx]++;
      } else if( chiHour < 16 ){
        chiTimeFrequency[1][monthIdx]++;
      } else{
        chiTimeFrequency[2][monthIdx]++;
      }
      
      if( localHour < 8 ){
        localTimeFrequency[0][monthIdx]++;
      } else if( localHour < 16 ){
        localTimeFrequency[1][monthIdx]++;
      } else{
        localTimeFrequency[2][monthIdx]++;
      }
      
      int latIdx = (int) ((lat+90)*indicesPerLatDegree);
      int lonIdx = (int) ((lon+180)*indicesPerLonDegree);
      int kIdx;
      //println("latIdx = " + latIdx + "lonIdx = " + lonIdx);
      if( quakeByLocation[latIdx][lonIdx] == null ){
        quakeByLocation[latIdx][lonIdx] = new ArrayList();
        numArrayLists++;
      //}
      //if( quakeByLocation[latIdx][lonIdx].isEmpty() ){
        quakeByLocation[latIdx][lonIdx].add(0, row);
        kIdx = 0;
        //println("creating array list " + latIdx + " " + lonIdx );
      } else{
        int i;
        for(i = 0; i < quakeByLocation[latIdx][lonIdx].size(); i++){
          //Earthquake e = (Earthquake) quakeByLocation[latIdx][lonIdx].get(i);
          int otherRow = (Integer) quakeByLocation[latIdx][lonIdx].get(i);
          float otherMag = data[otherRow][magnitudeColumn];
          if( otherMag < mag ){
            break;
          }
        }
        quakeByLocation[latIdx][lonIdx].add(i, row);
        kIdx = i;
    }
     quakeIdxByRow[row][latIdxCol] = latIdx;
     quakeIdxByRow[row][lonIdxCol] = lonIdx;
     quakeIdxByRow[row][kIdxCol] = kIdx;
     //quakeIdxByTime[row][magCol] = (int) mag;
     //quakeIdxByTime[row][depthCol] = (int) depth;
  }//end for loop
  println("done storing quakes " + numArrayLists);
  }
  
  void drawQuakes(){
      //println("start draw");
      int count1 = 0;
      //int count2 = 0;
    //println("minLatIdx to draw = " + minLatIdx + " and maxLatIdx = " + maxLatIdx);
    //println("minLonIdx to draw = " + minLonIdx + " and maxLonIdx = " + maxLonIdx);
    //println("_____");
    for(int latIdx = minLatIdx; latIdx < maxLatIdx; latIdx++){
     for(int lonIdx = minLonIdx; lonIdx < maxLonIdx; lonIdx++){
       int quakesDrawn = 0;
       if( quakeByLocation[latIdx][lonIdx] != null ){
         for(int i = 0; i < quakeByLocation[latIdx][lonIdx].size(); i++){      
           int row = (Integer) quakeByLocation[latIdx][lonIdx].get(i);
           if( row >= firstRow && row < lastRow ){
            float mag = data[row][magnitudeColumn];
              if(( mag < 4 && magnitudeSelected[0] ) || ( mag >= 4 && magnitudeSelected[(int) mag-3]) ){
                float depth = data[row][depthColumn];
                //println("depthSelected sub 0 " + depthSelected[0]);
                if( (depth < 70 && depthSelected[0]) || (depth >= 70 && depth < 300 && depthSelected[1]) || (depth >= 300 && depthSelected[2]) ){
                  float utcHour = data[row][hourColumn];
                  if( (utcHour < 8 && timeSelected[0]) || (utcHour >= 8 && utcHour < 16 && timeSelected[1]) || (utcHour >= 16 && utcHour < 24 && timeSelected[2]) ){
                    //println("I'm drawing a quake " + data[row][latitudeColumn] + " " + data[row][longitudeColumn]);
                    drawQuake( data[row][latitudeColumn], data[row][longitudeColumn], mag, depth, utcHour);
                    quakesDrawn++;
                    count1++;
                  }//end time
                }//end depth
              }//end mag
           }//end row if
           if( quakesDrawn == qPerClusterToDraw ){
             i = quakeByLocation[latIdx][lonIdx].size(); //will exit this quake's for loop
           }
         }//end if quake not null
       }//end for loop for percluster to draw
     }// end lon for loop
    }//end lat for loop
    //println("end draw");
  }//end method drawQuakes
  
  void drawQuake( float latitude, float longitude, float magnitude, float depth, float hour){
    Location l = new Location( latitude, longitude );
    Point2f p = map.locationPoint(l);
    if( mode == magMode){
      if( (int) magnitude < 4 ){
        fill( quakeMagColors[0] );
      } else {
        fill( quakeMagColors[ (int) magnitude - 3 ] );
      }
    } else if( mode == depthMode){
      if( depth < 70 ){
        fill( depthColors[0]);
      } else if(depth < 300){
        fill( depthColors[1]);
      } else{
        fill(depthColors[2]);
      }
    } else{
      if( hour< 8 ){
        fill(timeColors[0]);
      } else if( hour < 16){
        fill(timeColors[1]);
      } else{
        fill(timeColors[2]);
      }
    }
    noStroke();
    
    if( zoomLevel <= 32 ){
      allQuakeModeSize = 5.0;
    } else if( zoomLevel <= 512 ){
      allQuakeModeSize = 7.0;
    } else if( zoomLevel <= 4048){
      allQuakeModeSize = 9.0;
    } else{
      allQuakeModeSize = 11.0;
    }
    ellipse(p.x, p.y, allQuakeModeSize, allQuakeModeSize);
    if( mouseX < p.x+3 && mouseX>p.x-3 && mouseY>p.y-3&& mouseY<p.y+3 ){
     quakeHover( latitude, longitude, magnitude, depth );//, hour, day, month, year  **********Variables need to be called in the drawquake in the first place *******
    } 
  }
  
  //TODO*************************************************************************, float hour, float day, float month, float year 
  void quakeHover( float lat, float lon, float mag, float depth){
    //LABELS FOR TEXT AREA  /////ADD ADDITIONAL LABELS  rect( 10, windowHeight-950, 100, windowHeight-850);
    rectMode(CORNERS);
  fill(100, 100, 100, 125);
  rect( 10, windowHeight-950, 100, windowHeight-850);
  textAlign(LEFT);
  textSize(10);
  fill(255,255,255);
  text("Quake Info:\nMagnitude = " +mag+"\n Depth = "+depth+"\n Lat = "+lat+"\n Long = "+lon, 15, windowHeight - 930);   /////ADD ADDITIONAL LABELS
  ////+"\n YY:MM:DD = "+int(year)+":"+int(month)+":"+int(day)
  rectMode(CORNER);
    
  }
  
  void startBigQuakeAnimation(){
    //bigQuakeCount = 0;
  }
  
  //FOR BIG QUAKE MODE
  int drawQuakesFromRToR(int quakeFirstRow, int quakeLastRow){
    ellipseMode(CENTER);
    //println("i'm animating " + bigQuakeCount );
    //if( bigQuakeCount == 0 ){
      //bigQuakeCount = quakeFirstRow;
    //} else if( bigQuakeCount == quakeLastRow ){
      //do nothing
    //} else {
      //bigQuakeCount = bigQuakeCount+.5; //+= 1.0;
    //}
    int bigQuakeRow = 0; //init
    float lat, lon, mag, depth, utcHour, year, month;
    for(int row = quakeFirstRow; row <=  quakeLastRow; row++){
      lat = data[row][latitudeColumn];
      lon = data[row][longitudeColumn];
      mag = data[row][magnitudeColumn];
      depth = data[row][depthColumn];
      utcHour = data[row][hourColumn];
      year = Float.parseFloat( getRowName( row ) );
      month = data[row][monthColumn];
      
      if( mag > 8 ){//found the bigQuake idx
        bigQuakeRow = row;
      }
      
      Location l = new Location( lat, lon );
      Point2f p = map.locationPoint(l);
      if( mode == magMode){  
        if( (int) mag < 4 ){
          fill( quakeMagColors[0] );
        } else {
          fill( quakeMagColors[ (int) mag - 3 ] );
        }
      } else if( mode == depthMode){
        if( depth < 70 ){
          fill( depthColors[0]);
        } else if(depth < 300){
          fill( depthColors[1]);
        } else{
          fill(depthColors[2]);
        }
      } else{
        if( utcHour< 8 ){
          fill(timeColors[0]);
        } else if( utcHour < 16){
          fill(timeColors[1]);
        } else{
          fill(timeColors[2]);
        }
     }
     noStroke();
    
      if( zoomLevel <= 32 ){
        quakeModeSize = 7.0;
      } else if( zoomLevel <= 512 ){
        quakeModeSize = 8.0;
      } else if( zoomLevel <= 4048){
        quakeModeSize = 9.0;
      }
      
      if( bigQuakeRow == row ){
        quakeModeSize = 15.0;
      }
    
      ellipse(p.x, p.y, quakeModeSize, quakeModeSize);
    }//end for loop
    
    lat = data[bigQuakeRow][latitudeColumn];
    lon = data[bigQuakeRow][longitudeColumn];
    mag = data[bigQuakeRow][magnitudeColumn];
    depth = data[bigQuakeRow][depthColumn];
    utcHour = data[bigQuakeRow][hourColumn];
    
    Location l = new Location( lat, lon);
    Point2f p = map.locationPoint(l);
    
    if( mode == magMode){  
        if( (int) mag < 4 ){
          fill( quakeMagColors[0], 75 );
        } else {
          fill( quakeMagColors[ (int) mag - 3 ] , 75);
        }
      } else if( mode == depthMode){
        if( depth < 70 ){
          fill( depthColors[0] ,75);
        } else if(depth < 300){
          fill( depthColors[1], 75);
        } else{
          fill(depthColors[2], 75);
        }
      } else{
        if( utcHour< 8 ){
          fill(timeColors[0], 75);
        } else if( utcHour < 16){
          fill(timeColors[1], 75);
        } else{
          fill(timeColors[2], 75);
        }
     }
     noStroke();
     
     if( zoomLevel <= 16 ){
        quakeModeSize = 100.0;
      } else if( zoomLevel <= 64 ){
        quakeModeSize = 200.0;
      } else if( zoomLevel <= 512){
        quakeModeSize = 400.0;
      } else{
        quakeModeSize = (float) zoomLevel;
      }
    
    ellipse(p.x, p.y, quakeModeSize, quakeModeSize);
    if( abs(mouseX - p.x) < quakeModeSize && abs(mouseY - p.x) < quakeModeSize ){
      /////////
    }
    
    ellipseMode(CORNER);
    return bigQuakeRow;
  }//end method
  
  
  void setToMagMode(){
    mode = magMode;
  }
  
  void setToDepthMode(){
    mode = depthMode;
  }
  
  void setToTimeMode(){
    mode = timeMode;
  }
  
  float convertToLocalTime(float lat, float lon, float utcHour ){
    return utcHour + floor(lon/15);
  }
      
      
  float convertToChiTime(float utcHour){
    float chiTime = utcHour-6;
    if( chiTime < 0 ){
      chiTime = 24 + chiTime;
    }
    return chiTime;
  }
  
  int[][] getMagFreq(){
    //println("mag freq.length = " + magnitudeFrequency[0].length);
    return magnitudeFrequency;
  }
  
  int[][] getDepthFreq(){
    return depthFrequency;
  }
  
  int[][] getLocalTimeFreq(){
    return localTimeFrequency;  
  }
  
  int[][] getUTCFreq(){
    return utcFrequency;
  }
  
  int[][] getChiTimeFreq(){
    return chiTimeFrequency;
  }
  
  
  int getMagIndex(int row){
    int mag = (int) data[row][magnitudeColumn];
    if( mag < 4 ){
      return 0;
    } 
    return mag-3;
  }
  
  int getDepthIndex(int row){
    int depth = (int) data[row][magnitudeColumn];
    if( depth < 70 ){
      return 0;
    } else if( depth < 300 ){
      return 1;
    } 
    return 2;
  }
  
   float getLatitude( int row ){
     return data[row][latitudeColumn];
   }
   
    float getLongitude( int row ){
      return data[row][longitudeColumn];
    }
    
    float getMagnitude( int row ){
      return data[row][magnitudeColumn];
    }
    
    float getDepth( int row ){
      return data[row][depthColumn];
    }
    
    int getYear( int row ){
      return Integer.parseInt( getRowName( row ) );
    }
    
    int getMonth( int row ){
      return (int) data[row][monthColumn];
    }
    
    int getDay( int row ){
      return (int) data[row][dayColumn];
    }
    
    int getHour( int row){
      return (int) data[row][hourColumn];
    }
  
  
  int getRowCount() {
    return rowCount;
  }
  
  
  String getRowName(int rowIndex) {
    return rowNames[rowIndex];
  }
  
  
  String[] getRowNames() {
    return rowNames;
  }
  
  void freqSanityCheck(){
    for(int i = 0; i < magnitudeFrequency[0].length; i++){
      for( int j = 0; j < magnitudeFrequency.length; j++){
        //println( " magnitudeFrequency of month = " + i + " and category " +  j + " = " + magnitudeFrequency[j][i]);
      }
      //println("-------------------");
    }
  }
  
  int fromLatToLatIdx( float lat ){
     return (int) (indicesPerLatDegree*(lat+90));
  }
  
  int fromLonToLonIdx( float lon ){
    return (int) (indicesPerLonDegree*(lon+180));
  }
  
  void reSetLatAndLonRange(float newMinLat, float newMaxLat, float newMinLon, float newMaxLon, double zoomLevel){
    if( newMinLat >= -90 ){
      minLat = newMinLat;
      minLatIdx = fromLatToLatIdx( minLat);
    }
    if( newMaxLat <= 90 ){
      maxLat = newMaxLat;
      maxLatIdx = fromLatToLatIdx( maxLat);
    }
    if( newMinLon >= -180 ){
      minLon= newMinLon;
      minLonIdx = fromLonToLonIdx( minLon);
    }
    if( newMaxLon <= 180 ){
      maxLon = newMaxLon;
      maxLonIdx = fromLonToLonIdx( maxLon);
    }

    this.zoomLevel = zoomLevel;
    qPerClusterToDraw = (int) zoomLevel;
  }
  
  /*
  void GuanReSetLatAndLonRange(float newMinLat, float newMaxLat, float newMinLon, float newMaxLon){
    if( newMinLat >= -90 ){
      minLat = newMinLat;
      minLatIdx = fromLatToLatIdx( minLat);
    }
    if( newMaxLat <= 90 ){
      maxLat = newMaxLat;
      maxLatIdx = fromLatToLatIdx( maxLat);
    }
    if( newMinLon >= -180 ){
      minLon= newMinLon;
      minLonIdx = fromLonToLonIdx( minLon);
    }
    if( newMaxLon <= 180 ){
      maxLon = newMaxLon;
      maxLonIdx = fromLonToLonIdx( maxLon);
    }
  }
  */
  
  void resetMagSel(int catToChange){
    magnitudeSelected[ catToChange ] = !magnitudeSelected[ catToChange ];
  }
  
  void resetDepthSel(int catToChange){
    //println("before depthSelected in " + catToChange  + " is " + depthSelected[catToChange]); 
    depthSelected[ catToChange ] = !depthSelected[ catToChange ];
    //println("after depthSelected in " + catToChange  + " is " + depthSelected[catToChange]); 
  }
  
  void resetTimeSel(int catToChange){
    utcSelected[catToChange] = !utcSelected[catToChange];
  }
  
  
  void changeMinDate( int month, int year){
    if( year < Integer.parseInt(getRowName(0)) || year > Integer.parseInt(getRowName(rowCount-1))){
      return;
    }
    if( year > 2010 ){
      year = 2010;
      month = 6;
    }
    int mAndYIdx = 12*(year-1973) + (month-1);
    int mAndYRow = mAndYRows[mAndYIdx];
    firstRow = mAndYRow; 
  }
  
  void changeMaxDate(int month, int year){
    if( year < Integer.parseInt(getRowName(0)) || year > Integer.parseInt(getRowName(rowCount-1))){
      return;
    }
    if( year > 2010 ){
      year = 2010;
      month = 6;
    }
    if( year == 2010 && month > 6 ){
      year = 2010;
      month = 6;
    }
    int mAndYIdx = 12*(year-1973) + (month-1);
    int mAndYRow = mAndYRows[mAndYIdx + 1] - 1; //last row = last day of that selected month== next month boundary - 1
    lastRow = mAndYRow; 
  }
  
  /*
  void initQuakesAroundCity(float lat, float lon, int range, double newZoomLevel){
    float latDegChange = getLatDegreeInDist(lat, range);
    float lonDegChange = getLonDegreeInDist(lat, range);
    //GuanReSetLatAndLonRange( lat-latDegChange, lat+latDegChange, lon - lonDegChange, lon + lonDegChange);
    GuanReSetLatAndLonRange( lat-10, lat+10, lon - 10, lon + 10);
    zoomLevel = newZoomLevel;
  }
  
  void drawQuakesAroundCity(){//float lat, float lon, int range, double newZoomLevel){
    //float latDegChange = getLatDegreeInDist(lat, range);
    //float lonDegChange = getLonDegreeInDist(lat, range);
    //zoomLevel = newZoomLevel;
    //println("latdegChange = " + latDegChange + " and londegrchange " + lonDegChange);
    //GuanReSetLatAndLonRange( lat-latDegChange, lat+latDegChange, lon - lonDegChange, lon + lonDegChange); 
    //drawQuakes();
  //  drawQuakes();
  
    for(int latIdx = minLatIdx; latIdx < maxLatIdx; latIdx++){
     for(int lonIdx = minLonIdx; lonIdx < maxLonIdx; lonIdx++){
       //int quakesDrawn = 0;
       if( quakeByLocation[latIdx][lonIdx] != null ){
         for(int i = 0; i < quakeByLocation[latIdx][lonIdx].size(); i++){      
           int row = (Integer) quakeByLocation[latIdx][lonIdx].get(i);
           //if( row >= firstRow && row < lastRow ){
            float mag = data[row][magnitudeColumn];
              //if(( mag < 4 && magnitudeSelected[0] ) || ( mag >= 4 && magnitudeSelected[(int) mag-3]) ){
                float depth = data[row][depthColumn];
                //println("depthSelected sub 0 " + depthSelected[0]);
                //if( (depth < 70 && depthSelected[0]) || (depth >= 70 && depth < 300 && depthSelected[1]) || (depth >= 300 && depthSelected[2]) ){
                  float utcHour = data[row][hourColumn];
                  //if( (utcHour < 8 && timeSelected[0]) || (utcHour >= 8 && utcHour < 16 && timeSelected[1]) || (utcHour >= 16 && utcHour < 24 && timeSelected[2]) ){
                    drawQuake( data[row][latitudeColumn], data[row][longitudeColumn], mag, depth, utcHour);
                    //quakesDrawn++;
                    //count1++;
                  //}//end time
                //}//end depth
              //}//end mag
           //}end row if
           //if( quakesDrawn == qPerClusterToDraw ){
             //i = quakeByLocation[latIdx][lonIdx].size(); //will exit this quake's for loop
           //}
         }//end if quake not null
       }//end for loop for percluster to draw
     }// end lon for loop
    }//end lat for loop
    //println("end draw");
  //end method drawQuakes
  
    
  
  }
  */
  float getLatDegreeInDist(float lat, int desiredDistance){
    float absLat = abs(lat);
    float degreePerDistance = 1;
    if( absLat >= 0.0 && absLat < 7.5){
      degreePerDistance = 1/110.574;
    } else if( absLat >= 7.5 && absLat < 22.5 ){
      degreePerDistance = 1/110.649;
    } else if( absLat >= 22.5 && absLat < 37.5 ){
      degreePerDistance = 1/110.852;
    } else if( absLat >= 37.5 && absLat < 52.5 ){
      degreePerDistance = 1/111.132;
    } else if( absLat >= 52.5 && absLat < 67.5){
      degreePerDistance = 1/111.412;
    } else if( absLat >= 67.5 && absLat < 82.5 ){
      degreePerDistance = 1/111.618;
    } else {//if( absLat >= 82.5 && absLat <= 90.0 ){
      degreePerDistance = 1/111.694;
    }
    return degreePerDistance * desiredDistance;  
  }
  
  float getLonDegreeInDist(float lat, int desiredDistance){ // why lat?  it is the thing that influences lon degrees per km
    float absLat = abs(lat);
    float degreePerDistance = 1;
    if( absLat >= 0.0 && absLat < 7.5){
      degreePerDistance = 1/111.320;
    } else if( absLat >= 7.5 && absLat < 22.5 ){
      degreePerDistance = 1/107.551;
    } else if( absLat >= 22.5 && absLat < 37.5 ){
      degreePerDistance = 1/96.486;
    } else if( absLat >= 37.5 && absLat < 52.5 ){
      degreePerDistance = 1/78.847;
    } else if( absLat >= 52.5 && absLat < 67.5){
      degreePerDistance = 1/55.800;
    } else if( absLat >= 67.5 && absLat < 82.5 ){
      degreePerDistance = 1/28.902;
    } else{//if( absLat >= 82.5 && absLat <= 90.0 ){
      degreePerDistance = 1/14.000; //this is a bit of a lie (supposed to be zero but... that won't work)
                                    // how often will we really need the poles?
    }
    return degreePerDistance * desiredDistance;
  }
  
  
  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
  //String[] getYears(){
    //return rowNames;
  //}
  
  //String getYear(int rowIndex){
    //return rowNames[rowIndex];
  //}

  
  // Find a row by its name, returns -1 if no row found. 
  // This will return the index of the first row with this name.
  // A more efficient version of this function would put row names
  // into a Hashtable (or HashMap) that would map to an integer for the row.
  int getRowIndex(String name) {
    for (int i = 0; i < rowCount; i++) {
      if (rowNames[i].equals(name)) {
        return i;
      }
    }
    //println("No row named '" + name + "' was found");
    return -1;
  }
  
  
  // technically, this only returns the number of columns 
  // in the very first row (which will be most accurate)
  int getColumnCount() {
    return columnCount;
  }
  
  
  String getColumnName(int colIndex) {
    return columnNames[colIndex];
  }
  
  
  String[] getColumnNames() {
    return columnNames;
  }


  float getFloat(int rowIndex, int col) {
    // Remove the 'training wheels' section for greater efficiency
    // It's included here to provide more useful error messages
    
    // begin training wheels
    if ((rowIndex < 0) || (rowIndex >= data.length)) {
      throw new RuntimeException("There is no row " + rowIndex);
    }
    if ((col < 0) || (col >= data[rowIndex].length)) {
      throw new RuntimeException("Row " + rowIndex + " does not have a column " + col);
    }
    // end training wheels
    
    return data[rowIndex][col];
  }

   boolean isValid(int row, int col) {
    if (row < 0) return false;
    if (row >= rowCount) return false;
    //if (col >= columnCount) return false;
    if (col >= data[row].length) return false;
    if (col < 0) return false;
    return !Float.isNaN(data[row][col]);
  }
    
  float getColumnMin(int col) {
    float m = Float.MAX_VALUE;
    for (int row = 0; row < rowCount; row++) {
      if (isValid(row, col)) {
        if (data[row][col] < m) {
          m = data[row][col];
        }
      }
    }
    return m;
  }
 
  float getColumnMax(int col) {
    float m = -Float.MAX_VALUE;
    for (int row = 0; row < rowCount; row++) {
      if (isValid(row, col)) {
        if (data[row][col] > m) {
          m = data[row][col];
        }
      }
    }
    return m;
  }
  
  float getRowMin(int row) {
    float m = Float.MAX_VALUE;
    for (int col = 0; col < columnCount; col++) {
      if (isValid(row, col)) {
        if (data[row][col] < m) {
          m = data[row][col];
        }
      }
    }
    return m;
  } 


  float getRowMax(int row) {
    float m = -Float.MAX_VALUE;
    for (int col = 0; col < columnCount; col++) {
      if (isValid(row, col)) {
        if (data[row][col] > m) {
          m = data[row][col];
        }
      }
    }
    return m;
  }


  float getTableMin() {
    float m = Float.MAX_VALUE;
    for (int row = 0; row < rowCount; row++) {
      for (int col = 0; col < columnCount; col++) {
        if (isValid(row, col)) {
          if (data[row][col] < m) {
            m = data[row][col];
          }
        }
      }
    }
    return m;
  }


  float getTableMax() {
    float m = -Float.MAX_VALUE;
    for (int row = 0; row < rowCount; row++) {
      for (int col = 0; col < columnCount; col++) {
        if (isValid(row, col)) {
          if (data[row][col] > m) {
            m = data[row][col];
          }
        }
      }
    }
    return m;
  }
  
  float getColumnAverage(int col){
    if( rowCount == 0 ){
      return -1.0;
    }
    float sum = 0.0;
    for (int row = 0; row < rowCount; row++) {
      if (isValid(row, col)) {
          sum += data[row][col];
      }
    }
    return sum/rowCount;
  }
  
  float getColumnSum(int col){
    return getColumnAverage(col)*rowCount;
  }
  
  float getAverageInCategory(int categoryCol, float categoryValue, int colToAverage){
    float[] categoryValues = new float[1];
    categoryValues[0] = categoryValue;
    return getAverageInCategory(categoryCol, categoryValues, colToAverage);
  }
   
  float getAverageInCategory(int categoryCol, float categoryValues[], int colToAverage){
    int noOfCategories = categoryValues.length;
    float sum = 0.0;
    int count = 0;
    
    for(int row = 0; row < rowCount; row++){
      for( int i = 0; i < noOfCategories; i++){
        if( isValid(row, categoryCol) && data[row][categoryCol] == categoryValues[i]){
          if( isValid(row, colToAverage) ){
            sum+= data[row][colToAverage];
            count++;
          }//end if
        }//end if
      }//end for
    }//end for
    
    if( count == 0){
      return -1.0;
    }
    return sum/count;
  }//end method
  

  
  float getSumInCategory(int categoryCol, float categoryValue, int colToSum){
    float[] a = new float[1];
    a[0] = categoryValue;
    return getSumInCategory(categoryCol, a, colToSum);
  }
 
  
  float getSumInCategory(int categoryCol, float categoryValues[], int colToSum){
    int noOfCategories = categoryValues.length;
    float sum = 0.0;
    int count = 0;
    
    for(int row = 0; row < rowCount; row++){
      for( int i = 0; i < noOfCategories; i++){
        if( isValid(row, categoryCol) && data[row][categoryCol] == categoryValues[i]){
          if( isValid(row, colToSum) ){
            sum+= data[row][colToSum];
            count++;
          }//end if
        }//end if
      }//end for
    }//end for
    
    if( count == 0){
      return -1.0;
    }
    return sum;
  }
  
  
  float getCountInCategory(int categoryCol, float categoryValues[], int colToSum){
    int noOfCategories = categoryValues.length;
    int count = 0;
    
    for(int row = 0; row < rowCount; row++){
      for( int i = 0; i < noOfCategories; i++){
        if( isValid(row, categoryCol) && data[row][categoryCol] == categoryValues[i]){
          if( isValid(row, colToSum) ){
            count++;
          }//end if
        }//end if
      }//end for
    }//end for
    
    return count;
  }
  
 
  float getAverageFromRowToRow(int firstRow, int lastRow, int col){
    float count = 0.0;
    float sum = 0.0;
    for(int row = firstRow; row <= lastRow; row++){
      if(isValid(row, col)){
        sum += data[row][col];
        count++;
      }
    }
    if( count == 0){
      return -1.0;
    }
    return sum/count;
  }
  
  float getSumFromRowToRow(int firstRow, int lastRow, int col){
    float count = 0.0;
    float sum = 0.0;
    for(int row = firstRow; row <= lastRow; row++){
      if(isValid(row, col)){
        sum += data[row][col];
        count++;
      }
    }
    if( count == 0){
      return -1.0;
    }
    return sum;
  }
  
  float getCountFromRowToRow(int firstRow, int lastRow, int col){
    float count = 0.0;
    for(int row = firstRow; row <= lastRow; row++){
      if(isValid(row, col)){
        count++;
      }
    }
    return count;
  }
  
  float getCountFromRowToRowInCategory( int firstRow, int lastRow, int categoryCol, float categoryValues[], int colToCount){
    int noOfCategories = categoryValues.length;
    float count = 0.0;
    
    for(int row = firstRow; row <= lastRow; row++){
      if(isValid(row, colToCount)){
        count++;
      }
    }
    return count;
  }

 
}//end class


